#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <memory.h>
#include <malloc.h>
#include <errno.h>
#include <netinet/in.h>
#include "rfs-client.h"
int con_stblshd = 0;

int enviar(PACKAGE *package, struct sockaddr_in *sock, int flag) {
    return sendto(package->from, package, sizeof (PACKAGE), flag, (struct sockaddr *) sock, sizeof (*sock));
}

int recibir(PACKAGE *package, struct sockaddr_in *sock, int flag) {
    int sock_size = sizeof (*sock), n, i, j = 0;
    // recibo el paquete
    errno = EAGAIN;
    n = -1;
    for (i = 0; errno == EAGAIN && i <= TIMEOUT && n < 0; i++) {
        n = recvfrom(package->from, package, sizeof (PACKAGE), flag, (struct sockaddr *) sock, &sock_size);
        usleep(1000);
    }
    if (n < 0 && errno != EAGAIN) {
        printf("Error nº %i recibiendo paquete.\n", errno);
    } else
        if (n < 0 && errno == EAGAIN) {
        printf("Timeout recibiendo paquete.\n");
    }

    return n;
}

void print_res(char * res) {
    printf("Resultado\n");
    printf("-----------------\n");
    printf("%s\n", res);
    printf("-----------------\n");
}

int begin_conection(PACKAGE *package) {
    package->op = INIT;
    char * data_aux = malloc(sizeof (DATA_LENGTH));
    strcpy(data_aux, package->data);

    int n = -1, j = 0;
    do {
        enviar(package, &dir, 0);
        if ((n = recibir(package, NULL, MSG_DONTWAIT)) < 0) {
            printf("Falló el intento de conexión nº %i.\n", j + 1);
        } else {
            // Seteo la direccion y trato de conectarme.
            dir.sin_port = htons((int) ntohl(atoi(package->data)));
        }
    } while (n < 0 && j++ < RE_ATTEMPTS);
    if (n < 0)
        return ERROR;
    printf("===========================\n");
    printf("Fue negociada con éxito la conexión con el servidor,\nque otorgó el puerto '%i'.\n", ntohs(dir.sin_port));
    printf("===========================\n");
    strcpy(package->data, data_aux);
    free(data_aux);
    con_stblshd = 1;
    return OK;
}

int
rls(PACKAGE *package) {
    // inicio la conexión
    if (!con_stblshd && begin_conection(package) == ERROR)
        return ERROR;
    // envío el comando
    package->op = RLS;
    enviar(package, &dir, 0);
    // creo el puntero que va a contener el resultado final
    char output[MAX_STRING_SIZE];
    output[0] = '\0';

    do {
        // recibo el paquete
        if (recibir(package, NULL, MSG_DONTWAIT) < 0) {
            snprintf(output, MAX_STRING_SIZE, "Ocurrió un error recibiendo la respuesta.");
            continue;
        } else {
            // agrego la cadena que viene con el paquete
            strcat(output, package->data);
            package->op = ACK;
            enviar(package, &dir, 0);
        }
    } while (package->offset == DATA_LENGTH);

    // imprimo resultado
    print_res(output);

    // retorno cantidad de paquetes leídos
    return package->block_count;
}

int
rcd(PACKAGE *package) {

    // inicio la conexión
    if (!con_stblshd && begin_conection(package) == ERROR)
        return ERROR;

    // envío el comando
    package->op = RCD;
    enviar(package, &dir, 0);

    // creo el puntero que va a contener el resultado final
    char output[MAX_STRING_SIZE];
    output[0] = '\0';

    do {
        // recibo el paquete
        if (recibir(package, NULL, MSG_DONTWAIT) < 0) {
            snprintf(output, MAX_STRING_SIZE, "Ocurrió un error recibiendo la respuesta del puerto.");
            continue;
        } else {
            // agrego la cadena que viene con el paquete
            strcat(output, package->data);
            package->op = ACK;
            enviar(package, &dir, 0);
        }
    } while (package->offset == DATA_LENGTH);

    // imprimo resultado
    print_res(output);

    // retorno cantidad de paquetes leídos
    return package->block_count;
}

int
get(PACKAGE * package) {
    char * file_name = (char *) malloc(DATA_LENGTH);
    strncpy(file_name, package->data, DATA_LENGTH);

    // inicio la conexión
    if (!con_stblshd && begin_conection(package) == ERROR)
        return ERROR;

    FILE * file = NULL;

    // creo el puntero que va a contener el resultado final
    char output[MAX_STRING_SIZE];
    output[0] = '\0';

    package->block_count = 0;
    int n, j;
    do {
        n = -1, j = 0;
        do {
            package->op = GET;
            strncpy(package->data, file_name, DATA_LENGTH);
            enviar(package, &dir, 0);
            if ((n = recibir(package, NULL, MSG_DONTWAIT)) < 0) {
                snprintf(output, MAX_STRING_SIZE, "Ocurrió un error al solicitar al servidor el archivo '%s'.\n"
                        "El servidor no respondió a la solicitud.", file_name);
            } else
                if (package->op == OK) {
                if (file == NULL) {
                    file = fopen(file_name, "w");
                }
                // escribo el bloque del lado del cliente
                fwrite(package->data, sizeof (char), package->offset, file);
                // preparo la llamada siguiente
                package->block_count++;
                // traigo el siguiente bloque
            } else {
                snprintf(output, MAX_STRING_SIZE, "Ocurrió un error al intentar descargar el archivo.\n"
                        "El archivo '%s' no existe o el servidor no tiene permiso para leerlo.",
                        file_name);
                continue;
            }
        } while (n < 0 && j++ < RE_ATTEMPTS);
    } while (package->offset == DATA_LENGTH);
    if (file != NULL)
        // cierro el archivo
        fclose(file);
    if (strlen(output) == 0) {
        int i = package->block_count;
        i--;
        i *= DATA_LENGTH;
        i += package->offset;
        snprintf(output, MAX_STRING_SIZE, "Se descargó correctamente el archivo '%s'.\n"
                "Se transmitieron %i bytes en total.",
                file_name, i);
    }
    print_res(output);

    free(file_name);
    return package->block_count;
}

int
put(PACKAGE * package) {
    char * file_name = (char *) malloc(DATA_LENGTH);
    strncpy(file_name, package->data, DATA_LENGTH);

    // inicio la conexión
    if (!con_stblshd && begin_conection(package) == ERROR)
        return ERROR;

    // creo el puntero que va a contener el resultado final
    char output[MAX_STRING_SIZE];
    output[0] = '\0';

    FILE * file = NULL;
    file = fopen(file_name, "r");

    int n, j, k, l;
    if (file != NULL) {
        package->block_count = 0;
        do {
            n = -1, j = 0;
            do {
                package->op = PUT;
                strncpy(package->data, file_name, DATA_LENGTH);
                //envío comando y nombre de archivo
                enviar(package, &dir, 0);
                // recibo confirmación
                if ((n = recibir(package, NULL, MSG_DONTWAIT)) < 0) {
                    snprintf(output, MAX_STRING_SIZE, "Ocurrió un error al solicitar al servidor subir el archivo %s.\n"
                            "No se ha recibido el OK del servidor.", file_name);
                } else
                    // si no hubo error al crear el archivo en el servidor
                    if (package->op == OK) {

                    // leo el bloque
                    package->offset = fread(package->data, sizeof (char), DATA_LENGTH, file);
                    k = -1, l = 0;
                    do {
                        // envio el bloque
                        enviar(package, &dir, 0);
                        if ((k = recibir(package, NULL, MSG_DONTWAIT)) < 0) {
                            snprintf(output, MAX_STRING_SIZE, "Ocurrió un error recibiendo el ACK.");
                            continue;
                        } else {
                            if (package->op == ACK) {
                                // preparo la llamada siguiente
                                package->block_count++;
                            } else {
                                snprintf(output, MAX_STRING_SIZE, "El servidor envió '%i' cuando se esperaba ACK.");
                                continue;
                            }
                        }
                    } while (k < 0 && l++ < RE_ATTEMPTS);
                } else {
                    printf("0\n");
                    snprintf(output, MAX_STRING_SIZE, "Ocurrió un error al solicitar al servidor subir el archivo %s.\n"
                            "El servidor no pudo crear el archivo.", file_name);
                    continue;
                }
            } while (n < 0 && j++ < RE_ATTEMPTS);
            // hasta que haya leído y enviado una porción menor al tamaño máximo (señal del fin de archivo)
        } while (package->offset == DATA_LENGTH);
        if (file != NULL)
            // cierro el archivo
            fclose(file);
        if (strlen(output) == 0) {
            int i = package->block_count;
            i--;
            i *= DATA_LENGTH;
            i += package->offset;
            snprintf(output, MAX_STRING_SIZE, "Se subió correctamente el archivo '%s'.\n"
                    "Se transmitieron %i bytes en total.",
                    file_name, i);
        }
        free(file_name);
    } else {
        snprintf(output, MAX_STRING_SIZE, "El archivo %s no existe o no puede ser leído.", file_name);
    }
    print_res(output);
    return package->block_count;
}

int exit_client(PACKAGE * package) {
    if (!con_stblshd)
        return OK;
    int n = -1, j = 0;
    do {
        // seteo fin de sesión
        package->op = QUIT;
        // envio el bloque
        enviar(package, &dir, 0);
        if ((n = recibir(package, NULL, MSG_DONTWAIT)) < 0) {
            printf("Ocurrió un error recibiendo la respuesta del servidor al pedido de cierre de sesión.\n");
        }
    } while (n < 0 && j++ < RE_ATTEMPTS);

    if (n >= 0 && package->op == OK)
        return OK;

    return ERROR;
}
